//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//
// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//

#include <windows.h>
#include <windows.h>
#include <ceddk.h>
#include <types.h>
#include <string.h>
#include <stdio.h>
#include <tchar.h>
#include <nkintr.h>

#include "qci.h"
#include "qci_private.h"

#define REUSE_STILL_DMA_BUFFER 1

#define MAX_BUF_NUM 1200
#define MAX_FRAG_NUM 10
#define MAX_EMPTY_NUM 5

#define N_STILL_FRAMES 1
#define N_VIDEO_FRAMES 4

#define SINGLE_DESCRIPTOR_TRANSFER_MAX  4096

#define MAX_INTERVAL_TIME 10000
#define DESCRIPTIONS_NUM_UXGA  938
#define DESCRIPTIONS_NUM_QXGA  1536
frame_list_t *current_frame_list;
frame_t *video_frame_tail;
int still_skips;

static void frame_dma_link(frame_t *f1, frame_t *f2);
static void free_frame(frame_t* frame);
static frame_list_t* find_frame_list(format_t* format);
static void frame_free_dma_buf(frame_t *frame);
void dump_frame(frame_t *frame);

void dma_buf_alloc(dma_buf_t *buf, unsigned int size, BOOL cache)
{
    DMA_ADAPTER_OBJECT adapter;
    PHYSICAL_ADDRESS   phy_addr;

    adapter.ObjectSize    = sizeof (DMA_ADAPTER_OBJECT);
    adapter.InterfaceType = Internal;
    adapter.BusNumber     = 0;
    buf->buf = HalAllocateCommonBuffer(&adapter, size, &phy_addr, FALSE);
    buf->phy_addr = phy_addr.LowPart;
    buf->size = size;
    
    if (!buf->buf) {
        NKDbgPrintfW(TEXT("qci failed to alloc buffer %d!\r\n"), size);
        return;		
    }
    if (cache)
        buf->buf = MmMapIoSpace(phy_addr, size, TRUE);
}

void dma_buf_free(dma_buf_t *buf)
{
    PHYSICAL_ADDRESS phy_addr;

    if (buf->buf == NULL)
        return;

    memset(&phy_addr, 0, sizeof(phy_addr));
    HalFreeCommonBuffer(NULL, 0, phy_addr, (PVOID)buf->buf, 0);
    buf->buf = NULL;
}

#define DMA_DESC_SIZE (sizeof(XLLP_CI_DMAC_DESCRIPTOR_T))

XLLP_CI_DMAC_DESCRIPTOR_T *get_descriptors(plane_t *plane)
{
    return (XLLP_CI_DMAC_DESCRIPTOR_T*)plane->desc_buf.buf;
}

// Add the buffer into the buffer pool and generate the DMA chains for the buffer
static BOOL dma_chain_init(plane_t *plane,
                           unsigned int dma_chain)
{
    unsigned int i, n = 0;
    unsigned int size = 0;
    unsigned int remain = 0;
    XLLP_CI_DMAC_DESCRIPTOR_T *descs;
    unsigned int descs_n;
    unsigned int phy_addr;
    unsigned int buf_phy_addr;

    descs = (XLLP_CI_DMAC_DESCRIPTOR_T*)plane->desc_buf.buf;
    descs_n = plane->desc_buf.size / sizeof(*descs);
    phy_addr = plane->desc_buf.phy_addr;
    remain = plane->buf.size;
    buf_phy_addr = plane->buf.phy_addr;

    for (i = 0; i < descs_n; i++)
    {
        if (remain == 0)
        {
            NKDbgPrintfW(L"qci: dma buffer is smaller than expected!!!\r\n");
            return FALSE;
        }

        size = remain > SINGLE_DESCRIPTOR_TRANSFER_MAX?
                SINGLE_DESCRIPTOR_TRANSFER_MAX : remain;

        phy_addr += sizeof(*descs);
        descs[i].ddadr = phy_addr;
        descs[i].dsadr = XLLP_CI_REGBASE_PHY + dma_chain;
        descs[i].dtadr = buf_phy_addr;
        descs[i].dcmd = size | XLLP_CI_DMAC_DCMD_INC_TRG_ADDR;

        if (descs_n == DESCRIPTIONS_NUM_UXGA || descs_n == DESCRIPTIONS_NUM_QXGA) {
            if (i == 0) {
                descs[i].dcmd |= XLLP_CI_DMAC_DCMD_SOF_IRQ_EN;
            }
        }

        remain -= size;
        buf_phy_addr += size;
    }
    return TRUE;
}

static void dump_dma_chain(plane_t *plane)
{
    unsigned int i;
    XLLP_CI_DMAC_DESCRIPTOR_T *descs;
    unsigned int descs_n;

    descs = (XLLP_CI_DMAC_DESCRIPTOR_T*)plane->desc_buf.buf;
    descs_n = plane->desc_buf.size / sizeof(*descs);

    for (i = 0; i < descs_n; i++)
    {
        NKDbgPrintfW(TEXT("%d ddadr 0x%08x, dsadr 0x%08x, dtadr 0x%08x, dcmd 0x%08x\r\n"),
            i, descs[i].ddadr, descs[i].dsadr, descs[i].dtadr, descs[i].dcmd);
    }
}

// calculate frame buffer sizes by frame format and dimension
void get_buffer_desc_size(int format,
                          unsigned int width,
                          unsigned int height,
                          int plane_size[3],
                          int n_descs[3])
{
    unsigned int frame_size;
    int i;

    plane_size[0] = 0;
    plane_size[1] = 0;
    plane_size[2] = 0;

    switch(format) 
    {
    case XLLP_CAMERA_IMAGE_FORMAT_RAW10:
    case XLLP_CAMERA_IMAGE_FORMAT_RAW9:
    case XLLP_CAMERA_IMAGE_FORMAT_RGB565:
    case XLLP_CAMERA_IMAGE_FORMAT_YCBCR422_PACKED:
        frame_size = width * height * 2;
        plane_size[0] = frame_size;
        break;
    case XLLP_CAMERA_IMAGE_FORMAT_RGB888_PACKED:
        frame_size = width * height * 3;
        plane_size[0] = frame_size;
        break;
    case XLLP_CAMERA_IMAGE_FORMAT_YCBCR422_PLANAR:
        frame_size = width * height * 2;
        plane_size[0] = frame_size / 2;
        plane_size[1] = frame_size / 4;
        plane_size[2] = frame_size / 4;
        break;
    default:
        break;
    }

    for (i = 0; i < 3; i++)
        n_descs[i] = (plane_size[i] + SINGLE_DESCRIPTOR_TRANSFER_MAX - 1) 
                     / SINGLE_DESCRIPTOR_TRANSFER_MAX;
}

unsigned int round(unsigned int x, unsigned int y)
{
    return ((x + y - 1) / y) * y;
}

#define MIN(x, y) ((x) > (y)? (y) : (x))

static int chain_tbl[3] = { XLLP_CIBR0, XLLP_CIBR1, XLLP_CIBR2 };

BOOL frame_alloc_dma_buf(frame_t *frame)
{
    int plane_size[3];
    int n_descs[3];
    int i;
    BOOL cache;

    format_t* format = &frame->list->format;
  
    get_buffer_desc_size(format->format, format->width, format->height,
                         plane_size, n_descs);

    for (i = 0; i < 3; i++)
    {
        plane_t *plane = &frame->plane[i];
        int desc_buf_size;
        int rev = 0;

        if (!plane_size[i])
            break;

        if (format->is_still)
            cache = 0;
        else 
            cache = 1;
        dma_buf_alloc(&plane->buf, round(plane_size[i], 16), cache);

        if (!plane->buf.buf)
            goto clean;

        desc_buf_size = n_descs[i] * sizeof(XLLP_CI_DMAC_DESCRIPTOR_T);
        dma_buf_alloc(&plane->desc_buf, desc_buf_size, 0);
        if (!plane->desc_buf.buf)
            goto clean;


        if (!dma_chain_init(plane, chain_tbl[i]))
            goto clean;
    }

    return TRUE;

clean:
    frame_free_dma_buf(frame);
    return FALSE;
}

#ifdef REUSE_STILL_DMA_BUFFER
int start_index_for_reuse[3] = {-1, -1, -1};  // -1 means no still dma buffer to be reused.
format_t g_still_format;
BOOL isDmaSharingEnable = FALSE;

// Still mode and Preview mode share the same dma buffer.  So when running
// to one of these two mode, should switch the dma buffer descriptor to its
// correct setting.
XLLP_CI_DMAC_DESCRIPTOR_T g_descs_of_still[3][N_VIDEO_FRAMES];
XLLP_CI_DMAC_DESCRIPTOR_T g_descs_of_preview[3][N_VIDEO_FRAMES];
int g_descs_index_for_restore[3][N_VIDEO_FRAMES];
int g_descs_current_index[3] = {0};
char *g_descs_buf[3] = {0};

static BOOL reuse_dma_chain_init(plane_t *plane, int index)
{
    unsigned int i, n = 0;
    unsigned int size = 0;
    unsigned int remain = 0;
    XLLP_CI_DMAC_DESCRIPTOR_T *descs;
    unsigned int descs_n;
    unsigned int phy_addr;
    unsigned int buf_phy_addr;

    descs = (XLLP_CI_DMAC_DESCRIPTOR_T*)plane->desc_buf.buf;
    descs_n = plane->desc_buf.size / sizeof(*descs);
    phy_addr = plane->desc_buf.phy_addr;
    remain = plane->buf.size;
    buf_phy_addr = plane->buf.phy_addr;

    for (i = 0; i < descs_n; i++)
    {
        if (remain == 0)
        {
            NKDbgPrintfW(L"qci: dma buffer is smaller than expected!!!\r\n");
            return FALSE;
        }

        size = remain > SINGLE_DESCRIPTOR_TRANSFER_MAX?
                SINGLE_DESCRIPTOR_TRANSFER_MAX : remain;

        if (i == descs_n - 1) 
        {
            if (g_descs_current_index[index] >= N_VIDEO_FRAMES) {
                RETAILMSG(1, (TEXT("Out of range.\r\n")));
            }
            memcpy(&(g_descs_of_still[index][g_descs_current_index[index]]), &descs[i], sizeof(descs[i]));
            descs[i].dcmd = size | XLLP_CI_DMAC_DCMD_INC_TRG_ADDR;
            memcpy(&g_descs_of_preview[index][g_descs_current_index[index]], &descs[i], sizeof(descs[i]));
            g_descs_index_for_restore[index][g_descs_current_index[index]] = i + start_index_for_reuse[index];
        }

        remain -= size;
        buf_phy_addr += size;
    }
    return TRUE;
}


BOOL frame_reuse_dma_buf_of_still(frame_t *frame)
{
    int plane_size[3];
    int n_descs[3];
    int i;

    format_t* format = &frame->list->format;

    // 1. The still format must be same as preview format.
    // 2. Still dma buffer should be larger than preview dma buffer size.
    frame_list_t *pStillList = NULL;
    frame_t *pStillFrame = NULL;    

    pStillList = find_frame_list(&g_still_format);
    if (pStillList == NULL) {
        RETAILMSG(1, (TEXT("Cannot find the still dma list.\r\n")));
        return FALSE;
    }
    pStillFrame = pStillList->head;
  
    get_buffer_desc_size(format->format, format->width, format->height,
                         plane_size, n_descs);

    for (i = 0; i < 3; i++)
    {
        plane_t *plane = &frame->plane[i];
        int desc_buf_size;
        int rev = 0;
        plane_t *still_plane = &pStillFrame->plane[i];

        g_descs_buf[i] = pStillFrame->plane[i].desc_buf.buf;

        if (!plane_size[i])
            break;

        desc_buf_size = n_descs[i] * sizeof(XLLP_CI_DMAC_DESCRIPTOR_T);

        // Allocate desc_buf.
        plane->desc_buf.buf = still_plane->desc_buf.buf + start_index_for_reuse[i] * sizeof(XLLP_CI_DMAC_DESCRIPTOR_T);
        plane->desc_buf.phy_addr = still_plane->desc_buf.phy_addr + start_index_for_reuse[i] * sizeof(XLLP_CI_DMAC_DESCRIPTOR_T);
        plane->desc_buf.size = desc_buf_size;

        // Allocate buf.
        plane->buf.buf = still_plane->buf.buf + start_index_for_reuse[i] * SINGLE_DESCRIPTOR_TRANSFER_MAX;
        plane->buf.phy_addr = still_plane->buf.phy_addr + start_index_for_reuse[i] * SINGLE_DESCRIPTOR_TRANSFER_MAX;
        plane->buf.size = round(plane_size[i], 16);

        if (!reuse_dma_chain_init(plane, i))
        {
            RETAILMSG(1, (TEXT("Cannot init the reused dma buffer.\r\n")));
            return FALSE;
        }

        
        start_index_for_reuse[i] += n_descs[i];
        g_descs_current_index[i]++;
        if (g_descs_current_index[i] >= 4) {
            g_descs_current_index[i] = 3;
        }
    }
    

    return TRUE;
}

#endif

static void frame_free_dma_buf(frame_t *frame)
{
    unsigned int i;

    for (i = 0; i < 3; i++)
    {
        plane_t *plane = &frame->plane[i];

        dma_buf_free(&plane->buf);
        dma_buf_free(&plane->desc_buf);
    }
}

void frame_link(frame_t *f1, frame_t *f2)
{
    f1->next = f2;
    frame_dma_link(f1, f2);
}

// mark a frame before dma transfer.
// if a mark is overwritten, it means the frame is ready
const short dma_mark[4] = { 0x0, 0xffff, 0x0, 0xffff };

static char *get_mark_ptr(frame_t *frame)
{
    plane_t *plane = &frame->plane[0];
    dma_buf_t *buf = &plane->buf;
    return buf->buf + buf->size - sizeof(dma_mark);
}

static void frame_clean_cache(frame_t *frame)
{
    unsigned int i;

    for (i = 0; i < 3; i++)
        CacheRangeFlush(frame->plane[i].buf.buf, frame->plane[i].buf.size, CACHE_SYNC_DISCARD);
}
static void frame_mark(frame_t *frame)
{
    char *mark = get_mark_ptr(frame);

    memcpy(mark, dma_mark, sizeof(dma_mark));
}

static BOOL frame_check_mark(frame_t *frame)
{
    char *mark = get_mark_ptr(frame);

    return memcmp(mark, dma_mark, sizeof(dma_mark)) == 0;
}

static void frame_dma_link(frame_t *f1, frame_t *f2)
{
    unsigned int i;

    for (i = 0; i < 3; i++)
    {
        XLLP_CI_DMAC_DESCRIPTOR_T *descs;
        unsigned int descs_n;
        plane_t *p1 = &f1->plane[i];
        plane_t *p2 = &f2->plane[i];

        if (!p1->buf.buf)
            return;

        descs = (XLLP_CI_DMAC_DESCRIPTOR_T*)p1->desc_buf.buf;
        descs_n = p1->desc_buf.size / sizeof(*descs);

        descs[descs_n - 1].ddadr = p2->desc_buf.phy_addr;
    }
}

void frame_append(frame_t *frame)
{
    if (!frame->list)
        return;

    frame->next = NULL;
    frame_mark(frame);
    frame_clean_cache(frame);
    frame_dma_link(frame, frame);

    if (!frame->list->head)
        frame->list->head = frame;
    else
        frame_link(frame->list->tail, frame);

    frame->list->tail = frame;
}

frame_t *frame_take()
{
    frame_t *frame;

    frame = current_frame_list->head;

    if (frame == NULL)
        return NULL;

    /* to keep DMA running
     * the last frame should not be removed
     */
    if (frame->next == NULL)
        return NULL;

    if (frame_check_mark(frame))
        return NULL;

    current_frame_list->head = frame->next;

    return frame;
}

frame_t* alloc_frame(frame_list_t *list)
{
    frame_t *frame;

    frame = malloc(sizeof(frame_t));
    
    if (frame == NULL)
        return FALSE;

    memset(frame, 0, sizeof(frame_t));

    frame->list = list;

#ifndef REUSE_STILL_DMA_BUFFER
    if (!frame_alloc_dma_buf(frame))
            goto err_alloc_frame;
#else
    if (frame->list->format.is_still) 
    {
        if (!frame_alloc_dma_buf(frame))
            goto err_alloc_frame;
    } 
    else 
    {   
        // Check the still buffer is larger than preview buffer.
        if (g_still_format.width * g_still_format.height > 
                      frame->list->format.width * frame->list->format.height * N_VIDEO_FRAMES)
            isDmaSharingEnable = TRUE;
        else 
            isDmaSharingEnable = FALSE;
        
        if (isDmaSharingEnable) 
        {
            // Preview will use the still memory.
            if (!frame_reuse_dma_buf_of_still(frame))
                goto err_alloc_frame;
        }
        else
        {   // Not use DMA sharing because the still buffer is not enought for preview.
            if (!frame_alloc_dma_buf(frame))
                goto err_alloc_frame;
        }
    }
#endif

    return frame;

err_alloc_frame:
    free_frame(frame);
    return NULL;
}

static void free_frame(frame_t* frame)
{
#ifndef REUSE_STILL_DMA_BUFFER
    frame_free_dma_buf(frame);
#else
    if (isDmaSharingEnable) 
    {
        if (frame->list->format.is_still == TRUE) 
        {
            // Preview use the still buffer.  so don't free dma buffer in preview case.
            frame_free_dma_buf(frame);
        }
    }
    else
    {
        frame_free_dma_buf(frame);
    }
#endif
    free(frame);
}

void free_frame_list(frame_list_t* list)
{
    frame_t* i;
    frame_t* next;
    for (i = list->head; i; i = next)
    {
        next = i->next;
        free_frame(i);
    }
}

BOOL alloc_frame_list(frame_list_t *list)
{
    int i;
    int n;

    if (list->format.is_still)
        n = N_STILL_FRAMES;
    else
        n = N_VIDEO_FRAMES;

    for (i = 0; i < n; i++)
    {
        frame_t* tmp = alloc_frame(list);
        if (!tmp)
        {
            free_frame_list(list);
            return FALSE;
        }

        frame_append(tmp);
    }

    return TRUE;
}

#define MAX_FRAM_LIST 20
frame_list_t frame_list_tbl[MAX_FRAM_LIST];
unsigned int n_frame_list = 0;

BOOL prepare_format(format_t* format)
{
    frame_list_t *list = find_frame_list(format);

#ifdef REUSE_STILL_DMA_BUFFER
    XLLP_CI_DMAC_DESCRIPTOR_T *pDesc;
    int i;
#endif

#ifdef REUSE_STILL_DMA_BUFFER
    RETAILMSG(1, (TEXT("Use camera DMA share mode.\r\n")));
#endif

    if (list)
        return TRUE;
    
    memset(&frame_list_tbl[n_frame_list], 0, sizeof(frame_list_t));

    memcpy(&frame_list_tbl[n_frame_list].format, format, sizeof(*format));

    alloc_frame_list(&frame_list_tbl[n_frame_list]);
    if (frame_list_tbl[n_frame_list].head != NULL)
    {
        if (!video_frame_tail && !format->is_still)
            video_frame_tail = frame_list_tbl[n_frame_list].tail;

        //dump_frame_list(&frame_list_tbl[n_frame_list]);
        n_frame_list++;

#ifdef REUSE_STILL_DMA_BUFFER
        if (format->is_still == TRUE)
        {
            // Allocate the still dma buffer.
            RETAILMSG(1, (TEXT("Allocated the dma buffer for still format.  Store the format information now.\r\n")));
            g_still_format.format = format->format;
            g_still_format.is_still = format->is_still;
            g_still_format.width = format->width;
            g_still_format.height = format->height;
            start_index_for_reuse[0] = 0; // Still dma is allocated, so initialize the index for preview buffer to reuse.
            start_index_for_reuse[1] = 0; // Still dma is allocated, so initialize the index for preview buffer to reuse.
            start_index_for_reuse[2] = 0; // Still dma is allocated, so initialize the index for preview buffer to reuse.
        } 
        else if (isDmaSharingEnable)
        {
            for (i = 0; i < 3; i++)
            {
                pDesc = (XLLP_CI_DMAC_DESCRIPTOR_T*)g_descs_buf[i];
                if (pDesc != NULL)
                {
                    memcpy(&g_descs_of_preview[i][g_descs_current_index[i]], &pDesc[g_descs_index_for_restore[i][g_descs_current_index[i]]], sizeof(XLLP_CI_DMAC_DESCRIPTOR_T));            
                }
                else
                {
                    //RETAILMSG(1, (TEXT("pDesc == NULL.\r\n")));
                }
            }
        }
#endif
        
        return TRUE;
    }

    memset(&frame_list_tbl[n_frame_list], 0, sizeof(frame_list_t));
    return FALSE;
}

static frame_list_t* find_frame_list(format_t* format)
{
    unsigned int i;

    for (i = 0; i < n_frame_list; i++)
    {
        frame_list_t* list = &frame_list_tbl[i];

        if (!list->head)
            continue;

        if (memcmp(&list->format, format, sizeof(format_t)) == 0)
            return list;
    }

    return NULL;
}


BOOL set_current_frame_list(format_t* format)
{
    current_frame_list = find_frame_list(format);

    // dump_frame_list(current_frame_list);
    return current_frame_list != NULL;
}

void dma_load()
{
    int i;
    frame_t* f;

#ifdef REUSE_STILL_DMA_BUFFER
    XLLP_CI_DMAC_DESCRIPTOR_T *pDesc;
    int j;
#endif

    if (!current_frame_list)
        return;

    if (!current_frame_list->head)
        return;

#ifdef REUSE_STILL_DMA_BUFFER
    
    if (isDmaSharingEnable) 
    {
    
        for (j = 0; j < 3; j++)
        {
            pDesc = (XLLP_CI_DMAC_DESCRIPTOR_T*)g_descs_buf[j];
            
            if (pDesc == NULL)
                continue;
            
            if (current_frame_list->format.is_still)
            {
                for (i = 0; i <= g_descs_current_index[j]; i++)
                {
                    memcpy(&g_descs_of_preview[j][i], &pDesc[g_descs_index_for_restore[j][i]], sizeof(XLLP_CI_DMAC_DESCRIPTOR_T));
                    memcpy(&pDesc[g_descs_index_for_restore[j][i]], &g_descs_of_still[j][i], sizeof(XLLP_CI_DMAC_DESCRIPTOR_T));
                }
            } 
            else 
            {
                for (i = 0; i <= g_descs_current_index[j]; i++)
                {
                    memcpy(&pDesc[g_descs_index_for_restore[j][i]], &g_descs_of_preview[j][i], sizeof(XLLP_CI_DMAC_DESCRIPTOR_T));
                }        
            }    
        }
    }
#endif

    for (f = current_frame_list->head; f; f = f->next)
    {
        frame_mark(f);
        frame_clean_cache(f);
    }

    for (i = 0; i < 3; i++)
        XllpCIDMALoadDescriptor(ci_reg,
                                current_frame_list->head->plane[i].desc_buf.phy_addr,
                                XLLP_CI_DMA_CHANNEL_0 + i);
}

void dma_save()
{
#ifdef REUSE_STILL_DMA_BUFFER    
    int i;
    int j;
    XLLP_CI_DMAC_DESCRIPTOR_T *pDesc;
    
    if (isDmaSharingEnable)
    {

        for (j = 0; j < 3; j++)
        {
            pDesc = (XLLP_CI_DMAC_DESCRIPTOR_T*)g_descs_buf[j];
            //RETAILMSG(1, (TEXT("+dma_save(). g_descs_buf[%d] = %08X\r\n"), j, g_descs_buf[j]));

            if (pDesc == NULL)
            {
                continue;
            }

            // If stop capture in preview mode, we need save current preview dma descriptor.
            if (current_frame_list->format.is_still == FALSE)
            {
                //RETAILMSG(1, (TEXT("is still = %d\r\n"), current_frame_list->format.is_still));
                for (i = 0; i <= g_descs_current_index[j]; i++)
                {
                    memcpy(&g_descs_of_preview[j][i], &pDesc[g_descs_index_for_restore[j][i]], sizeof(XLLP_CI_DMAC_DESCRIPTOR_T));
                }
            } 
        }
        //RETAILMSG(1, (TEXT("-dma_save().\r\n")));
    }
#endif

}

void dump_frame_list(frame_list_t *frame_list)
{
    frame_t *i;
    NKDbgPrintfW(L"format %d", frame_list->format.format);
    NKDbgPrintfW(L"width %d", frame_list->format.width);
    NKDbgPrintfW(L"height %d", frame_list->format.height);
    NKDbgPrintfW(L"head 0x%08x", frame_list->head);
    NKDbgPrintfW(L"tail 0x%08x", frame_list->tail);
    for (i = frame_list->head; i; i = i->next)
        dump_frame(i);
}

void dump_frame(frame_t *frame)
{
    int i;
    // unsigned int j;
    short *buf;

    NKDbgPrintfW(L"dumping frame 0x%08x", frame);

    if (!frame)
        return;

    for (i = 0; i < 3; i++)
    {
        NKDbgPrintfW(L"descriptor 0x%08x, phyiscal address 0x%08x \r\n", 
            frame->plane[i].desc_buf.buf,
            frame->plane[i].desc_buf.phy_addr);
        dump_dma_chain(&frame->plane[i]);
    }

    for (i = 0; i < 10; i++)
        NKDbgPrintfW(L"0x%02x ", frame->plane[0].buf.buf[i]);

    NKDbgPrintfW(L"dumping frame mark");

    buf = (short*)get_mark_ptr(frame);

    for (i = 0; i < 4; i++)
        NKDbgPrintfW(L"0x%04x ", buf[i]);
}

int still_skips;

TCHAR* INTERRUPT_DESCRIPTION[] = 
{
    L"EOF3",            //= ( 1UL << 0    ),    // End-of-Frame for Channel 3, depending on Channel 0 DMA channel descriptor Chain configuration
    L"FU_3",            //= ( 1UL << 1    ),    // FIFO Underrun for Channel 3, masked by XLLP_CI_INT_FU
    L"RESERVE",
    L"EOF",             //= ( 1UL << 3    ),    // End-of-Frame, masked by XLLP_CI_INT_EOF
    L"SOF",             //= ( 1UL << 4    ),    // Start-of-Frame, masked by XLLP_CI_INT_SOF
    L"CDD",             //= ( 1UL << 5    ),    // Quick Capture Interface Disable Done, masked by XLLP_CI_INT_CDD
    L"CQD",             //= ( 1UL << 6    ),    // Quick Capture Interface Quick Disable Status, masked by XLLP_CI_INT_CQD .
    L"PAR_ERR",         //= ( 1UL << 7    ),    // Parity Error, masked by XLLP_CI_INT_PAR_ERR
    L"EOL",             //= ( 1UL << 8    ),    // End of Line, masked by XLLP_CI_INT_EOL
    L"HST_INT",         //= ( 1UL << 9    ),    // Histogram Interrupt, unmaskable
    L"CGC_INT",         //= ( 1UL << 10 ),    // Compander Interrupt, unmaskable
    L"IFO_0",            //= ( 1UL << 11 ),    // FIFO Overrun for Channel 0, masked by XLLP_CI_INT_IFO
    L"IFO_1",            //= ( 1UL << 12 ),    // FIFO Overrun for Channel 1, masked by XLLP_CI_INT_IFO
    L"IFO_2",            //= ( 1UL << 13 ),    // FIFO Overrun for Channel 2, masked by XLLP_CI_INT_IFO
    L"IFO_3",            //= ( 1UL << 14 ),    // FIFO Overrun for Channel 3, masked by XLLP_CI_INT_IFO
    L"FTO",             //= ( 1UL << 15 ),    // FIFO Time-out, masked by XLLP_CI_INT_FTO
    L"BS0",             //= ( 1UL << 16 ),    // Branch Status for Channel 0, masked by XLLP_CI_INT_BS
    L"BS1",             //= ( 1UL << 17 ),    // Branch Status for Channel 1, masked by XLLP_CI_INT_BS
    L"BS2",             //= ( 1UL << 18 ),    // Branch Status for Channel 2, masked by XLLP_CI_INT_BS
    L"BS3",             //= ( 1UL << 19 ),    // Branch Status for Channel 3, masked by XLLP_CI_INT_BS
    L"SOF0",            //= ( 1UL << 20 ),    // Start-of-Frame for Channel 0, depending on Channel 0 DMA channel descriptor Chain configuration
    L"SOF1",            //= ( 1UL << 21 ),    // Start-of-Frame for Channel 1, depending on Channel 1 DMA channel descriptor Chain configuration
    L"SOF2",            //= ( 1UL << 22 ),    // Start-of-Frame for Channel 2, depending on Channel 2 DMA channel descriptor Chain configuration
    L"SOF3",            //= ( 1UL << 23 ),    // Start-of-Frame for Channel 3, depending on Channel 3 DMA channel descriptor Chain configuration
    L"SC0",             //= ( 1UL << 24 ),    // Stop interrupt Channel 0, masked by XLLP_CI_INT_SC0
    L"SC1",             //= ( 1UL << 25 ),    // Stop interrupt Channel 1, masked by XLLP_CI_INT_SC1
    L"SC2",             //= ( 1UL << 26 ),    // Stop interrupt Channel 2, masked by XLLP_CI_INT_SC2
    L"SC3",             //= ( 1UL << 27 ),    // Stop interrupt Channel 3, masked by XLLP_CI_INT_SC3
    L"BUSERR",            //= ( 1UL << 28 ),    // Bus error in One or more DMA channels.
    L"RESERVE",
    L"EOFX",            //= ( 1UL << 30 )    // End-of-Frame Transferred to Memory (Channel 0-2, not Channel 3), masked by XLLP_CI_INT_EOFX
    L"SINT",            //= ( 1UL << 31 ),    // Subsequent Interrupt Status, unmaskable
};

void displayInterruptStatus(int status)
{
    TCHAR buf[256];
    int i;
    wsprintf(buf, L"Interrup: %08X:", status);
    for (i = 0; i < 32; i++)
    {
        if (status & (1UL << i))
        {
            lstrcat(buf, INTERRUPT_DESCRIPTION[i]);
            lstrcat(buf, L",");
        }
    }

    RETAILMSG(1, (TEXT("%s\r\n"), buf));
}

DWORD WINAPI qci_intr_thread()
{
    int prio = 0;
#define MAX_READY_FRAME 10
    frame_t *frames[MAX_READY_FRAME];
    int n = 0;
    int i;
    
    UINT time;
    UINT elapseTime;
    XLLP_OST_T *pOst = (XLLP_OST_T *)ost_reg;
    BOOL isSkipped = FALSE;
    int skippedFrame;

    prio = CeGetThreadPriority(GetCurrentThread());
//  NKDbgPrintfW(TEXT("default priority %d!\r\n"), prio);
    //CeSetThreadPriority(GetCurrentThread(), 1);

    while(1) 
    {
        int status;
        void qci_dump_regs();
        format_t* format;

        status = WaitForSingleObject(qci_intr_event, INFINITE);

        if (status == WAIT_FAILED)
           NKDbgPrintfW(TEXT("CAMIntrThread: wait failed!\r\n"));
        // qci_dump_regs();

        if (current_frame_list == NULL)
        {
            NKDbgPrintfW(L"no dma frames!!!");
            continue;
        }

        format = &current_frame_list->format;
        // Get interrupt status.  If it's NOT EOF interrupt, we need to ignore it.
        status = XllpCIGetInterruptStatus(ci_reg);
        if (format->is_still)
        {
            //displayInterruptStatus(status);
            if ((status & XLLP_CI_INTSTATUS_EOF) == 0)
            {
                // Not EOF interrupt.
                if (status & XLLP_CI_INTSTATUS_SOF)
                    time = pOst->oscr0;                    
                else if (status & XLLP_CI_INTSTATUS_SOF0) 
                {
                    elapseTime = pOst->oscr0 - time;
                    //RETAILMSG(1, (TEXT("time = %d.  elapseTime = %d."), time, elapseTime));
                    if (elapseTime > MAX_INTERVAL_TIME)
                        isSkipped = TRUE;
                }
            
                //RETAILMSG(1, (TEXT("SOF or SOF0 interrupt is coming. Timing: %d.\r\n"), ((XLLP_OST_T *)ost_reg)->oscr0));
                XllpCIClearInterruptStatus(ci_reg, status);
                InterruptDone(qci_sys_intr);
                continue;
            }
            
            if (status & XLLP_CI_INTSTATUS_SOF) {
                XllpCIClearInterruptStatus(ci_reg, status);
                InterruptDone(qci_sys_intr);
                continue;
            }
            
            if (still_skips)
            {
                still_skips--;
                skippedFrame = 0;
            } else
            {
                if (!isSkipped) 
                {
                    // dump_frame_list(current_frame_list);
                    callback(current_frame_list->head);
                    RETAILMSG(1, (TEXT("skippedFrame = %d.\r\n"), skippedFrame));
                } else {
                    // Skip this frame.  And re-start capture a new frame.
                    skippedFrame++;
                    XllpCIClearInterruptStatus(ci_reg, 0xFFFFFFFF);
                    InterruptDone(qci_sys_intr);
                    isSkipped = FALSE;
                    
                    // Re-start capture a new image.
                    capture_start(0);
                    continue;
                }
            }
            isSkipped = FALSE;
            XllpCIClearInterruptStatus(ci_reg, 0xFFFFFFFF);
            InterruptDone(qci_sys_intr);
            continue;
        } else if ((status & XLLP_CI_INTSTATUS_EOF) == 0)
        {
            // SOF interrupt for preview.  Just ignore it.
            XllpCIClearInterruptStatus(ci_reg, status);
            InterruptDone(qci_sys_intr);
            continue;
        }

        // dump_frame_list(current_frame_list);

        n = 0;
        while (1) 
        {
            frames[n] = frame_take();

            if (!frames[n])
                break;
            n++;
            if (n >= MAX_READY_FRAME)
                break;
        }

        for (i = 0; i < n; i++)
        {
            callback(frames[i]);
            frame_append(frames[i]);
        }

        // dump_frame_list(current_frame_list);
  
        XllpCIClearInterruptStatus(ci_reg, 0xFFFFFFFF);
        InterruptDone(qci_sys_intr);
    }


    return TRUE;
}
